<?php
/**
 * Dependency abstract listener
 *
 * PHP version 5
 *
 * @category  W
 * @package   Services
 * @author    Olivier Hoareau <olivier@phppro.fr>
 * @copyright 2009 PHPPRO http://www.phppro.fr
 * @license   http://dev.phppro.fr/license PHPPRO
 * @version   SVN: $Id:$
 * @link      http://dev.phppro.fr/projects/wdependency
 */
require_once 'W/W.php';
require_once 'W/Dependency/Listener/Interface.php';
/**
 * Dependency abstract listener
 *
 * @category  W
 * @package   Services
 * @author    Olivier Hoareau <olivier@phppro.fr>
 * @copyright 2009 PHPPRO http://www.phppro.fr
 * @license   http://dev.phppro.fr/license PHPPRO
 * @version   SVN: $Id$
 * @link      http://dev.phppro.fr/projects/wdependency
 */
abstract class W_Dependency_Listener_Abstract implements W_Dependency_Listener_Interface
{
    /**
     * List of include paths
     *
     * @var array
     */
    private $includePaths;
    private $filters;
    /**
     * The data
     *
     * @var array
     */
    private $data;
    /**
     * Constructs a new listener
     *
     * @return W_Dependency_Listener_Interface
     */
    public function __construct ()
    {
        $this->includePaths = array();
        $this->filters      = array();
    }
    public function configure($options=null)
    {
        if (false === isset($options['filters'])) {
            return $this;
        }
        $this->filters = array_values($options['filters']);
        return $this;
    }
    public function listFilters()
    {
        return $this->filters;
    }
    /**
     * List all expected events
     *
     * @return W_Dependency_Listener_Interface
     */
    public function listExpectedEvents ()
    {
        return array('start' , 'stop');
    }
    /**
     * Sets the include paths to use
     *
     * @param array|string $includePaths
     *
     * @return W_Dependency_Listener_Interface
     */
    public function setIncludePaths ($includePaths)
    {
        if (false === is_array($includePaths)) {
            $includePaths = explode(PATH_SEPARATOR, $includePaths);
        }
        $this->includePaths = $includePaths;
        return $this;
    }
    /**
     * Returns the list of include paths
     *
     * @return array
     */
    public function getIncludePaths ()
    {
        return $this->includePaths;
    }
    public function parseRawDependency($dependency,$file)
    {
        if (0 === strlen($dependency)) {
            return null;
        }
        if (0 < preg_match('|^[a-zA-Z0-9\./\\\_\-]+$|',$dependency)) {
            return $dependency;
        }
        return null;
    }
    /**
     * Returns the real path of the specified file
     *
     * @param string $dependency the file
     *
     * @return string|null
     */
    public function getIncludedFileRealPathIfExists ($dependency,$file)
    {
        $dependency = $this->parseRawDependency($dependency,$file);
        if (null === $dependency) {
            return null;
        }
        if (0 === count($this->includePaths)) {
            if (false === W_W::fs()->isExistingFile($dependency)) {
                return null;
            }
            return str_replace('\\','/',W_W::fs()->getRealPath($dependency));
        }
        foreach ($this->includePaths as $includePath) {
            $realPath = $includePath . '/' . $dependency;
            if (true === W_W::fs()->isExistingFile($realPath)) {
                return str_replace('\\','/',W_W::fs()->getRealPath($realPath));
            }
        }
        if (true === W_W::fs()->isExistingFile($dependency)) {
            return str_replace('\\','/',W_W::fs()->getRealPath($dependency));
        }
        return null;
    }
    /**
     * Processes the specified event
     *
     * @param string $type the event type to process
     * @param array  $data the optional data of the event
     *
     * @return array list of optional additional files to analyze
     *
     * @throws RuntimeException if an error occured
     */
    public function processEvent ($type, $data = null)
    {
        $files = array();
        switch ($type) {
            case 'start':
                if (null === $this->data || false === is_array($this->data)) {
                    $this->data = array();
                }
                $this->setIncludePaths($data['includePaths']);
                break;
        }
        return $files;
    }
    /**
     * Returns all the data
     *
     * @return array
     */
    public function getData ()
    {
        return $this->data;
    }
    /**
     * Adds a file dependency
     *
     * @param string $dependency
     *
     * @return array list of additional files to analyze
     */
    protected function addFileDependency ($dependency,$parentFile,$root,$type)
    {
        $parentFile = str_replace('\\','/',$parentFile);
        $files = array();
        $fullPath = $this->getIncludedFileRealPathIfExists($dependency,$parentFile);
        $dType = 'unknown';
        $value = $dependency;
        if (null !== $fullPath) {
            $dType = 'file';
            $value = str_replace('\\','/',$fullPath);
            $files[] = $value;
        }
        $value = preg_replace('|^'.$root.'[/]?(.+)$|','\\1',$value);
        $parentFile = preg_replace('|^'.$root.'[/]?(.+)$|','\\1',$parentFile);
        if (false === isset($this->data[$parentFile])) {
            $this->data[$parentFile] = array();
        }
        if (false === isset($this->data[$parentFile][$type])) {
            $this->data[$parentFile][$type] = array();
        }
        $this->data[$parentFile][$type][] = array('type' => $dType , 'value' => $value);
        return $files;
    }

    /**
     * Adds a class dependency
     *
     * @param string $dependency the class dependency
     *
     * @return array list of additional files to analyze
     */
    protected function addClassDependency ($dependency,$parentFile,$root,$type,$parentClass=null)
    {
        $parentFile = str_replace('\\','/',$parentFile);
        $files = array();
        $newDependency = $this->parseRawDependency($dependency,$parentFile);
        if(null !== $newDependency) {
            $dependency = $newDependency;
        }
        $classIncludePath = str_replace('_', '/', $dependency) . '.php';

        $classFullPath = $this->getIncludedFileRealPathIfExists($classIncludePath,$parentFile);

        if (null !== $parentClass) {
            $parentPackage = str_replace('/','_',dirname(str_replace('_','/',$parentClass)));
            $package = str_replace('/','_',dirname(str_replace('_','/',$dependency)));
            $this->addPackageDependency($package,$parentPackage);
        }

        $dType = 'unknown';
        $value = $dependency;
        if (null !== $classFullPath) {
            $dType = 'file';
            $value = $classFullPath;
            $files[] = $value;
        }
        $value = preg_replace('|^'.$root.'[/]?(.+)$|','\\1',$value);
        $found = true;
        $filters = $this->listFilters();
        if (0 < count($filters)) {
            $found = false;
            foreach($filters as $filter) {
                if (0 < preg_match('|^'.$filter.'|',$value)) {
                    $found = true;
                    break;
                }
            }
        }
        if (false === $found) {
            return $files;
        }
        $parentFile = preg_replace('|^'.$root.'[/]?(.+)$|','\\1',$parentFile);
        if (false === isset($this->data[$parentFile])) {
            $this->data[$parentFile] = array();
        }
        if (false === isset($this->data[$parentFile][$type])) {
            $this->data[$parentFile][$type] = array();
        }
        $this->data[$parentFile][$type][] = array('type' => $dType , 'value' => $value);

        return $files;
    }

    /**
     * Adds a class in file
     *
     * @param string $name the class name
     *
     * @return array list of additional files to analyze
     */
    protected function addFileClass ($name,$file,$root,$type,$project)
    {
        $file = str_replace('\\','/',$file);
        $file = preg_replace('|^'.$root.'[/]?(.+)$|','\\1',$file);
        if (false === isset($this->data[$file]['class'])) {
            $this->data[$file]['class'] = array();
        }
        $this->data[$file]['class'][] = $name;
        if (false === isset($this->data[$name])) {
            $this->data[$name] = array();
        }
        $this->data[$name]['project'][] = $project;
        return array();
    }

    protected function addPackageDependency($subNodePackage,$nodePackage)
    {
        if ('.' === $subNodePackage) {
            $subNodePackage = 'Root';
        }
        if ('.' === $nodePackage) {
            $nodePackage = 'Root';
        }

        $this->data[$nodePackage]['package'][] = array('value'=>$subNodePackage);
        return $this;
    }
}